/**
 * @Description: 拖动控制判断类
 * @author Beon
 * @date 2021/7/5
*/
import ErrorMsg, {DropError} from '../decorators/errorMsg';
import Container from '../abstract/container';
import Widget from '../abstract/widget';
import DropBox from '../abstract/dropBox';
import Plant from '../abstract/plant';
import RevealList from '../abstract/revealList';
import DropMovingDom from './dropMovingDom';
import DomCreate from './domCreate';
import ToolsDec from '../decorators/toolsDec';
import Utils from './utils';

export type CanDropInMap = Record<string, boolean>

enum PosUpOrDown{
	Up,
	Down
}

export default class DropControl{
	// 传递操作element
	$el: HTMLElement
	// 使用的构造类
	$struct: Plant | Container
	// 可拖动map
	private canDropInMap: Record<string, boolean>
	// dropbox虚拟dom为了获取高度
	static noShowDropBox: HTMLElement = DomCreate.createNoShowDropBox();

	@ErrorMsg.dropError('可拖动区域')
	private static dropError: DropError

	constructor(props, struct) {
	    this.$el = props.element;
	    this.$struct = struct;
	}

	/**
	 * 初始化可拖动map
	 * @param DropList
	 */
	initDropInMap(DropList: string[]): void {
	    this.canDropInMap = DropList.reduce((all: CanDropInMap, item: string): CanDropInMap => {
	        all[item] = true;
	        return all;
	    }, {});
	}

	/**
	 * 判断是否可以拖动
	 * @param checkDrop
	 * @returns {boolean}
	 */
	canDropIn(checkDrop: string): boolean {
	    if (!this.canDropInMap) {
		    DropControl.dropError('请先初始化可拖动map');
	        return false;
	    }

	    return !!this.canDropInMap[checkDrop];
	}

	/**
	 * 静态方法，添加新的内容
	 * @param DropBoxObj
	 * @param e
	 */
	@ToolsDec.functionDoingOne
	static async setDropBox(DropBoxObj: Container | Widget, e: MouseEvent): Promise<void | boolean> {
	    DropMovingDom.hideDom();
	    const setInsideBox = this.checkPlantSite(e.target);
	    if (setInsideBox === undefined) {return false;}
	    let {realBox, startNum} = this.getRealBox(setInsideBox, e);

	    if (realBox === DropBoxObj.$parent) {
		    startNum > DropBoxObj.$index && startNum--;
	        if (DropBoxObj.$index !== startNum) {
		        realBox.dropControl.removeDropBox(DropBoxObj);
		        await this.realSetDropBox(realBox, startNum, DropBoxObj);
	        }
	    } else {
		    DropBoxObj.$parent && DropBoxObj.$parent.dropControl.removeDropBox(DropBoxObj);
	        DropBoxObj.setParent(realBox);
		    await this.realSetDropBox(realBox, startNum, DropBoxObj);
	    }
	}

	/**
	 * 移除一个DropBox
	 * @param DropBoxObj
	 * @param realBox
	 */
	removeDropBox(DropBoxObj: Container | Widget): void {
	    const dealDom = this.$struct instanceof Plant ? this.$el : this.$el.children[0];
	    DropControl.addTransition(() => {
	        dealDom.removeChild(DropBoxObj.$el);
	    }, DropBoxObj.$el, true);
	    this.$struct.insideBox.splice(DropBoxObj.$index, 1);
	    DropControl.reGetIndex(this.$struct, DropBoxObj.$index);
	}

	/**
	 * 真正添加的方法
	 * @param realBox
	 * @param startNum
	 * @param DropBoxObj
	 * @returns {Promise<void>}
	 */
	static async realSetDropBox(realBox: Container | Plant, startNum: number, DropBoxObj: Container | Widget): Promise<void> {
	    realBox.insideBox.splice(startNum, 0, DropBoxObj);
	    await realBox.dropControl.dealDropBox(DropBoxObj.$el, realBox.insideBox[startNum + 1]);
	    this.reGetIndex(realBox, startNum);
	}

	/**
	 * 处理可拖动容器、控件 TODO 这一块的内层dom判断需要再进行设计
	 * @param dom
	 * @returns {Promise<void>}
	 * @param afterBox
	 */
	async dealDropBox(dom: HTMLElement, afterBox: DropBox | Plant | undefined): Promise<void> {
	    const dealDom = this.$struct instanceof Plant ? this.$el : this.$el.children[0];
	    if (!afterBox) {
		    DropControl.addTransition(() => {
			    dealDom.append(dom);
		    }, dom);
	    } else {
		    DropControl.addTransition(() => {
			    dealDom.insertBefore(dom, afterBox.$el);
		    }, dom);
	    }
	}

	/**
	 * 为视图区变化添加动画
	 * @param fn
	 * @param dom
	 * @param isClose
	 */
	static addTransition(fn: () => void, dom: HTMLElement, isClose?: boolean): void {
	    let DomHeight;
	    if (isClose) {
	        DomHeight = dom.scrollHeight;
	        const setDom = dom;
		    // @ts-ignore
		    dom = dom.cloneNode(true);
		    setDom.parentElement.insertBefore(dom, setDom);
	    } else {
	        DropControl.noShowDropBox.append(dom);
	        DomHeight = dom.scrollHeight;
	        DropControl.noShowDropBox.removeChild(dom);
	    }
	    dom.style.height = (isClose ? DomHeight : 0) + 'px';
	    fn();
	    setTimeout(() => {
	        dom.style.height = (isClose ? 0 : DomHeight) + 'px';
	        setTimeout(() => {
	            dom.style.height = '';
	            isClose && dom.parentElement.removeChild(dom);
	        }, 380);
	    });
	}

	/**
	 * 静态方法，检查选中目标
	 * @param el
	 * @returns {Container | Widget | Plant | undefined}
	 */
	static checkPlantSite(el: any): DropBox | Plant | undefined {
	    while (el && el !== (Plant.stopEl?.parentElement || Symbol())) {
	        if (el.getAttribute('dropBox')) {
	            const backDropBox = DropBox.getDropBox(el.getAttribute('dropBox'));
	            if (backDropBox instanceof RevealList) {
	                return undefined;
	            }
	            return backDropBox;
	        } else {
	            el = el.parentElement;
	        }
	    }
	    return undefined;
	}

	/**
	 * 拖动移动时数据处理
	 * @param e
	 */
	static dropMove(e: MouseEvent): void | boolean {
	    const setInsideBox = this.checkPlantSite(e.target);
	    if (setInsideBox === undefined) {
		    DropMovingDom.hideDom();
		    return false;
	    }
	    DropMovingDom.showDom();
	    const {realBox, startNum} = this.getRealBox(setInsideBox, e);

	    const topHeight = realBox.insideBox.filter((item, index) => index < startNum).reduce((all, item) => all + Utils.getDocumentPos(item.$el).height, 0);
	    realBox.dropControl.movingDropBox(topHeight);
	}

	/**
	 * 处理拖拽位置信息并展示
	 * @param topHeight
	 */
	movingDropBox(topHeight: number): void {
	    const dealDom = this.$struct instanceof Plant ? this.$el : (this.$el.children[0] as HTMLElement);
	    const Pos = Utils.getDocumentPos(dealDom);
	    const paddingTop = this.$struct instanceof Plant ? this.$struct.plantPaddingTop : 0;
	    const {top, left, width} = {top: Pos.top + topHeight + paddingTop, left: Pos.left, width: Pos.width};
	    DropMovingDom.moveDom(top, left, width);
	}

	// /**
	//  * 拖拽移动开始
	//  */
	// static dropStart(): void {
	//     DropMovingDom.showDom();
	// }

	/**
	 * 处理获取真实dom对象
	 * @param setInsideBox
	 * @param e
	 * @returns {{realBox: any, startNum: any}}
	 */
	static getRealBox(setInsideBox: DropBox | Plant, e: MouseEvent): {realBox: Container | Plant, startNum: number} {
	    let realBox, startNum;
	    // 判断可容纳容器
	    if (setInsideBox instanceof Widget) {
	        realBox = setInsideBox.$parent;
	        startNum = this.posUpOrDown(e, setInsideBox.$el) === PosUpOrDown.Up ? setInsideBox.$index : setInsideBox.$index + 1;
	    } else {
	        realBox = setInsideBox;
	        startNum = realBox.insideBox.length;
	    }

	    return {
	    	realBox,
		    startNum
	    };
	}

	/**
	 * 判断鼠标是在dom的上半层还是下半层
	 * @param e
	 * @param dom
	 * @returns {PosUpOrDown.Up | PosUpOrDown.Down}
	 */
	static posUpOrDown(e: MouseEvent, dom: HTMLElement): PosUpOrDown {
	    const myPos = Utils.getDocumentPos(dom);
	    return e.clientY - myPos.top < myPos.height / 2 ? PosUpOrDown.Up : PosUpOrDown.Down;
	}

	/**
	 * 重新处理数组中的位置信息（避免重复大量计算）
	 * @param realBox
	 * @param startNum
	 */
	static reGetIndex(realBox: Plant | Container, startNum: number): void {
	    for (let i = startNum; i < realBox.insideBox.length; i++) {
	        realBox.insideBox[i].$index = i;
	    }
	}
}
